import os
import bpy
import shutil
import struct

class ccVertexData:

  def __init__ (self, x=0.0, y=0.0, z=0.0, nx=0.0, ny=0.0, nz=1.0, tu0=0.0, tv0=0.0):
    self.id = -1
    self.x = x
    self.y = y
    self.z = z
    self.nx = nx
    self.ny = ny
    self.nz = nz
    self.tu0 = tu0
    self.tv0 = tv0

  def setId (self, id):
    self.id = id

  def getId (self):
    return self.id

  def getX(self):
    return self.x
  def getY(self):
    return self.y
  def getZ(self):
    return self.z
  def getNX(self):
    return self.nx
  def getNY(self):
    return self.ny
  def getNZ(self):
    return self.nz
  def getTU0(self):
    return self.tu0
  def getTV0(self):
    return self.tv0

  def equals(self, o):
    return self.x == o.getX() and self.y == o.getY() and self.z == o.getZ() and self.nx == o.getNX() and self.ny == o.getNY() and self.nz == o.getNZ() and self.tu0 == o.getTU0() and self.tv0 == self.getTV0()
  

class ccTrigonData:
  def __init__ (self, id0 = 0, id1 = 0, id2 = 0):
    self.id = -1
    self.id0 = id0
    self.id1 = id1
    self.id2 = id2

  def setId(self, id):
    self.id = id
  def getId(self):
    return self.id

  def getId0 (self):
    return self.id0
  def getId1 (self):
    return self.id1
  def getId2 (self):
    return self.id2

  def equals (self, o):
    v0 = self.id0 == o.getId0() or self.id0 == o.getId1() or self.id0 == o.getId2()
    v1 = self.id1 == o.getId0() or self.id1 == o.getId1() or self.id1 == o.getId2()
    v2 = self.id2 == o.getId0() or self.id2 == o.getId1() or self.id2 == o.getId2()
    return v0 and v1 and v2

class ccUniqueData:

  def __init__(self):
    self.data = []

  def add (self, value):
    for d in self.data:
      if d.equals (value):
        return d.getId ()

    value.setId (len(self.data))
    self.data.append (value)
    return value.getId()

  def getRawData (self):
    return self.data

  def getNumberOfData (self):
    return len(self.data)

  def getData(self, idx):
    return self.data[idx]


class ccMeshData:

  def __init__(self, mesh):
    self.name = mesh.name
    self.mesh = mesh
    self.vertices = ccUniqueData()
    self.trigons = ccUniqueData()

  def priv_extractVertex(self, face, face_vertex_idx):
    # the vertex data is the data of the specified vertex
    idx = face.vertices[face_vertex_idx]
    vd = self.mesh.vertices[idx]

    x = vd.co[0]
    y = vd.co[1]
    z = vd.co[2]

    nx = vd.normal[0]
    ny = vd.normal[1]
    nz = vd.normal[2]

    if not face.use_smooth:
      nx = face.normal[0]
      ny = face.normal[1]
      nz = face.normal[2]
    
    tu = 0.0
    tv = 0.0

    if (self.mesh.uv_textures != None) and (len(self.mesh.uv_textures) > 0):
      # always use the first layer as the default layer
      uv_layer = self.mesh.uv_textures[0]
      uv_layer_face = uv_layer.data[face.index]

      tu = uv_layer_face.uv[face_vertex_idx][0]
      tv = uv_layer_face.uv[face_vertex_idx][1]

    vertex = ccVertexData (x, y, z, nx, ny, nz, tu, tv)
    return self.vertices.add (vertex)


  def priv_extractTrigon (self, face):
    vid0 = self.priv_extractVertex (face, 0)
    vid1 = self.priv_extractVertex (face, 1)
    vid2 = self.priv_extractVertex (face, 2)

    trigon = ccTrigonData (vid0, vid2, vid1)
    self.trigons.add (trigon)


  def priv_extractQuad (self, face):
    vid0 = self.priv_extractVertex (face, 0)
    vid1 = self.priv_extractVertex (face, 1)
    vid2 = self.priv_extractVertex (face, 2)
    vid3 = self.priv_extractVertex (face, 3)

    trigon = ccTrigonData (vid0, vid2, vid1)
    self.trigons.add (trigon)

    trigon = ccTrigonData (vid0, vid3, vid2)
    self.trigons.add (trigon)


  def priv_extractFace (self, face):
    if len (face.vertices) == 3:
      self.priv_extractTrigon (face)
    else:
      self.priv_extractQuad (face)

  def getName (self):
    return self.name

  def getVertexData (self):
    return self.vertices


  def getTrigonData (self):
    return self.trigons

  def extractData (self):
    print ("ExtractingData of mesh: %s\n" % self.mesh.name)

    for face in self.mesh.polygons:
      # range on the indices for the base mapping
      self.priv_extractFace (face)


class ccMeshDataExporter:
  def __init__(self, meshData):
    self.meshData = meshData

  def priv_writeVertices(self):
    vertices = self.meshData.getVertexData ()
    self.file.write (struct.pack ('I', vertices.getNumberOfData ()))
    for vd in vertices.getRawData ():
      self.file.write (struct.pack ('ffffffff', vd.getX(), vd.getY(), vd.getZ(), vd.getNX(), vd.getNY(), vd.getNZ(), vd.getTU0(), vd.getTV0()))

  def priv_writeTrigons(self):
    trigons = self.meshData.getTrigonData ()
    size = 16
    if self.meshData.getVertexData ().getNumberOfData () > 65536:
      size = 32

    self.file.write (struct.pack ('B', size))
    self.file.write (struct.pack ('I', trigons.getNumberOfData ()))
    for td in trigons.getRawData ():
      if size == 32:
        self.file.write (struct.pack('III', td.getId0(), td.getId1(), td.getId2()))
      else:
        self.file.write (struct.pack('HHH', td.getId0(), td.getId1(), td.getId2()))



  def output(self, filename):
    self.file = open (filename, "wb")
    # write a version info
    self.file.write (struct.pack ('I', 1))

    #self.priv_writeStandardVertexDeclaration ()
    self.priv_writeVertices ()
    self.priv_writeTrigons ()

    self.file.close ()


class ccGroupExporter:
  def testFloat (self, value, rhs):
    v = rhs - value
    return v > -0.0001 and v < 0.0001

  def isMatrixIdentity (self, matrix):
    for i in range (4):
      for j in range (4):
        if i == j:
          c = 1.0
        else:
          c = 0.0
        if not self.testFloat (matrix[i][j], c):
          return 0
    return 1

  def export (self, path, filename, basePath):
    self.path = path
    self.filename = filename

    if (os.path.exists (path)):
      shutil.rmtree(path)
    os.mkdir (path)
       
    for mesh in bpy.data.meshes:
      md = ccMeshData(mesh)
      md.extractData ()

      mesh_filename = path + "/" + mesh.name + ".mesh"

      mde = ccMeshDataExporter (md)
      mde.output (mesh_filename)


    # first check if there is more then one layer in use
    # in that case we should extract an LOD node at base

    # for each layer we have to check, if there is more than
    # one object within the layer. in that case we must put an
    # dummy root node for each layer.
    
    # the first ten layer are used for defining lod
    # start with a count of zero elements within each layer
    layer_count = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    for obj in bpy.data.objects:
      data = obj.data
      if not isinstance (data, bpy.types.Mesh):
        continue
      for l in range (10):
        if obj.layers[l]:
          layer_count[l] = layer_count[l] + 1;

    # now check how many lod layers we are using
    num_layers_used = 0;
    for l in range(10):
      if layer_count[l] > 0:
        num_layers_used = num_layers_used + 1;
    

    self.file = open(filename, "wt")


    # if we use more than one lod layer we have to put an lod node around them
    if num_layers_used > 1:
      self.file.write ("node:type=lod")
      self.file.write ("{")
      # everything root node (also the dummy - 'root' nodes beyond the lod node) are 
      # oriented around the center
      self.file.write ("transformation{matrix:1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1;}")
      self.file.write ("layers")
      self.file.write ("{")

    # now... we export all possible lod layers containing objects
    # if we are not within the context of an node: type=lod there is only
    # one layer filled within objects ... so we don't have to worry that
    for layer in range (10):
      # just skip the empty layers
      if layer_count[layer] == 0:
        continue

      # if the layer has more than one node, put in a dummy node as group
      is_first_object_matrix_identity = 0
      for obj in bpy.data.objects:
        data = obj.data
        if not isinstance (data, bpy.types.Mesh) or not obj.layers[layer]:
          continue
        is_first_object_matrix_identity = self.isMatrixIdentity (obj.matrix_world)
        break


      if layer_count[layer] > 1 or not is_first_object_matrix_identity:
        self.file.write ("node:type=node")
        self.file.write ("{")
        self.file.write ("transformation{matrix: 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1;}")


      for obj in bpy.data.objects:
        data = obj.data
        if not isinstance (data, bpy.types.Mesh) or not obj.layers[layer]:
          continue
        self.priv_writeObject (obj, basePath)

      if layer_count[layer] > 1 or not is_first_object_matrix_identity:
        self.file.write ("}")

    if num_layers_used > 1:
      self.file.write ("}")
      self.file.write ("}")
        
    self.file.close ()

  def priv_writeObject (self, obj, basePath):
    mw = obj.matrix_world
    mesh = obj.data
    mesh_name = "meshes/" + basePath + "/" + mesh.name + ".mesh"

    material_name = "default"
    if (obj.active_material != None):
      material_name = obj.active_material.name


    self.file.write ("node:type=entity")
    self.file.write ("{")

    self.file.write ("transformation")
    self.file.write ("{")
    self.file.write ("matrix:%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f;" % (mw[0][0], mw[1][0], mw[2][0], mw[3][0], mw[0][1], mw[1][1], mw[2][1], mw[3][1], mw[0][2], mw[1][2], mw[2][2], mw[3][2], mw[0][3], mw[1][3], mw[2][3], mw[3][3] ))
    self.file.write ("}")

    self.file.write ("geometry")
    self.file.write ("{")

    self.file.write ("staticmesh")
    self.file.write ("{")
    self.file.write ("filename:%s;" % mesh_name)
    self.file.write ("}")

    self.file.write ("material:%s;" % material_name)

    self.file.write ("}")

    self.file.write ("}")



class Exporter (bpy.types.Operator):
  bl_idname = "cc.export"
  bl_label = "Select Export File"
  
  filepath = bpy.props.StringProperty (subtype="FILE_PATH")
  
  def invoke (self, context, evt):
    context.window_manager.fileselect_add (self)
    return {'RUNNING_MODAL'}
  
  def execute (self, context):
    pn = os.path.splitext(self.filepath)

    bn = os.path.basename (self.filepath)
    bn = os.path.splitext(bn)
    bn = bn[0]

    exporter = ccGroupExporter ()
    exporter.export (pn[0], self.filepath, bn)
    return {'FINISHED'}
    
    
bpy.utils.register_class(Exporter)
bpy.ops.cc.export ('INVOKE_DEFAULT')

